我們已經討論過 JavaScript 的同步性(逐字逐行執行)
那非同步回呼(asynchronus callbacks)是什麼?
非同步表示在一個時間點不只執行一段程式,
可能有一段程式在執行時會開始執行另一段程式碼,然後又會再執行別的程式碼,
這些程式碼在 JavaScript 引擎內是同時在執行的,
但之前說過 JavaScript 是同步的,
它不會非同步的執行,
它一次執行一行程式碼,
首先我們需要思考 JavaScript 引擎本身,
例如瀏覽器裡面還有其他東西,
有其他引擎,在 JavaScript 引擎外執行別的程式,
像是排版引擎(Rendering Engine),可以依據 HTML 與 CSS 繪製出網頁的內容到瀏覽器畫面上,
JavaScript 引擎可以和排版引擎溝通來改變網頁的樣子,
JavaScript 引擎有時也需要處理瀏覽器的 HTTP 請求來撈取 API 資料,
排版引擎和 HTTP 請求在瀏覽器內是非同步執行的,
裡面只有 JavaScript 引擎是同步執行的
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
讓我們看一下,
我們已經學了執行堆,
當函數被呼叫時這些執行環境被創造,
依照函數被呼叫的順序,最後被呼叫的函數會在執行堆最上面,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
函數執行結束後,離開執行堆,
然而 JavaScript 引擎內的等待列則稱為事件佇列(event queue),
這裡面都是事件、事件通知,這些可能要發生的,
所以當瀏覽器,在 JavaScript 引擎外的某處,有需要被通知的事件,
事件(非同步)會被放到 JavaScript 引擎的事件佇列裡,
我們可能有函數需要回應它,
我們可以監聽這個事件,事件發生時會觸發對應的函數,
事件會被放到事件佇列裡,
所以例如我有個 click 事件,如果在瀏覽器畫面裡點擊,
會先執行在執行堆中被呼叫的函數裡的每行程式敘述,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
所以我的 b 函數執行完畢,離開執行堆上方,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
現在回到執行 a 函數,然後將 a 函數執行完,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
然後回到全域執行環境,將剩下的程式碼執行完,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
然後當執行堆是空的 JavaScript 才會注意事件佇列,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
當執行堆是空的 JavaScript 會注意事件佇列,
它預期那邊會有東西,
如果有,它會看是否有函數會被這個事件觸發,
所以它看到 click 事件,處理 click 事件之後知道有一個函數需要執行,
所以在 click 事件發生時會觸發對應的函數,會創造執行環境給那個函數,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
然後這個事件就處理完畢,
繼續處理佇列中的下一個事件,
圖片來源:JavaScript 全攻略:克服 JS 的奇怪部分課程第 2 節講座 18 影片截圖
如此循序將事件佇列中的事件執行下去,
這稱為持續檢查(continuous check),
來看看一些程式碼,
程式碼如下:
// long running function
function waitThreeSeconds() {
var ms = 3000 + new Date().getTime();
while(new Date() < ms) {}
console.log('finished function');
}
function clickHandler() {
console.log('click event be emit!');
}
// listen for the click event
window.addEventListener('click', clickHandler, false);
waitThreeSeconds();
console.log('finished execution');
如果我在網頁剛開始載入就點擊瀏覽器,
這3個 console.log 的結果在 Chrome 的 Console 中出現的順序為何?
我們直接來看:
跟你想的一樣嗎?
因為所有的函數會先被執行,當執行堆被清空,
JavaScript 才會處理事件佇列中的事件,並依序執行對應的函數,將函數放進執行堆中執行,
這表示長時間函數可以干擾事件,
再說一次,非同步執行程式碼在 JavaScript 是可能的,
但非同步的部分是發生在 JavaScript 引擎外,
透過事件佇列可以讓 JavaScript 達成類似非同步執行程式碼的效果,
當執行堆中的程式碼依序執行完清空時,才會執行事件佇列中的非同步事件,然後依序處理它們,但是同步的處理,
在執行堆中一次執行一個觸發事件所對應的函數。